package org.summerframework.component.security.security.service.impl;

import org.summerframework.component.security.AdminSave;
import org.summerframework.component.security.Constants;
import org.summerframework.component.security.LoginData;
import org.summerframework.component.security.RoleResultCode;
import org.summerframework.component.security.context.RequestContext;
import org.summerframework.component.security.core.SecurityResultCode;
import org.summerframework.component.security.core.config.LoginProperties;
import org.summerframework.component.security.events.*;
import org.summerframework.component.security.permissions.service.UserPermissionsRoleRelService;
import org.summerframework.component.security.permissions.service.UserPermissionsService;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsDTO;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsQueryDTO;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsRoleRelDTO;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsRoleRelQueryDTO;
import org.summerframework.component.security.role.service.UserRoleRelService;
import org.summerframework.component.security.role.service.UserRoleService;
import org.summerframework.component.security.role.service.dto.UserRoleDTO;
import org.summerframework.component.security.role.service.dto.UserRoleRelDTO;
import org.summerframework.component.security.role.service.dto.UserRoleRelQueryDTO;
import org.summerframework.component.security.security.service.DefaultRoleService;
import org.summerframework.component.security.events.*;
import org.summerframework.core.base.result.DefaultResultCode;
import org.summerframework.core.base.service.BaseService;
import org.summerframework.core.exception.WebException;
import org.summerframework.core.util.OptionalUtils;
import org.summerframework.core.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.web.method.HandlerMethod;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.summerframework.component.security.core.interceptor.SecurityInterceptor.ADMIN;
import static org.summerframework.component.security.core.interceptor.SecurityInterceptor.ANY;

/**
 * 用户的角色权限组织机构的抽象查询
 * <p>
 * 需要实现存储方式
 *
 * @author 石超
 * @version v1.0.0
 */
@Slf4j
public abstract class AbstractRoleServiceImpl implements DefaultRoleService {
    private final Object LOCK = new Object();
    @Resource
    private UserRoleRelService userRoleRelService;
    @Resource
    private UserPermissionsService userPermissionsService;
    @Resource
    private UserRoleService userRoleService;
    @Resource
    private UserPermissionsRoleRelService userPermissionsRoleRelService;
    @Resource
    private FindByIndexNameSessionRepository<Session> sessionRepository;
    @Resource
    private AdminSave adminSave;
    @Resource
    private LoginProperties loginProperties;

    String getResdisKey(Object userId) {
        return ROLE_SAVE_KEY + userId;
    }

    abstract void put(String userId, String key, List list);

    @Override
    public boolean isRole(String role, HandlerMethod handlerMethod) {
        RequestContext<?> context = RequestContext.getContext();

        if (!context.getLoginData().isLogin()) {
            throw new WebException(SecurityResultCode.ERROR_NOT_LOGIN);
        }

        List<UserRoleRelDTO> userRoleRelDTOS = this.get(context.getLoginData().getUserId().toString(), ROLE_KEY);
        List<String> roles = userRoleRelDTOS.stream().map(UserRoleRelDTO::getRoleCode).collect(Collectors.toList());
        List<String> permissions = this.get(context.getLoginData().getUserId().toString(), PERMISSIONS_LIST_KEY);


        try {
            if (adminSave.isAdmin(context.getLoginData().getUserId()) || roles.contains(ADMIN) || role.equals(ANY)) {//如果是ADMIN用户 获取ANY
                return true;
            } else if (role.startsWith("ROLE_")) {//判断角色
                String roleName = role.substring(5);
                if (roles.contains(roleName)) {
                    return true;
                }
            } else {
                UserPermissionsDTO userPermissionsDTO = OptionalUtils.get(userPermissionsService.selectByCode(role), SecurityResultCode.ERROR_STAT_NOT_ROLE.getMsg());

                if (userPermissionsDTO.getStatus() == 0) { //权限被停用
                    throw new WebException(RoleResultCode.ERROR_PERMISSIONS_DISABLE);
                } else if (permissions.contains(role)) { //是否拥有权限
                    return true;
                }
            }
        }
        //catch (SelectNullException e) {
        //    log.warn("权限未查询到, {}", e.getCause().getMessage());
        //    throw new WebException(SecurityResultCode.ERROR_STAT_NOT_ROLE, e);
        //}
        catch (WebException e) {
            throw e;
        } catch (Exception e) {
            throw new WebException(DefaultResultCode.SYSTEM_ERROR, e);
        }

        if (userRoleRelDTOS.stream().anyMatch(status -> status.getStatus() == 0)) {
            throw new WebException(SecurityResultCode.ERROR_STAT_NOT_ROLE);
        } else {
            throw new WebException(SecurityResultCode.ERROR_STAT_NOT_ONE_ROLE);
        }
    }

    @Override
    public boolean supportsRole(String role) {
        return true;
    }

    @Override
    public void postAuthentication(LoginData loginData) {
        parse(loginData.getUserId().toString(), false);
    }

    /**
     * 解析菜单
     *
     * @param userId 用户id
     */
    @Override
    public void parse(String userId) {
        parse(userId, true);
    }

    /**
     * 解析菜单
     *
     * @param userId 用户id
     */
    private void parse(String userId, boolean flag) {
        LocalDateTime now = LocalDateTime.now();
        new ParseRunnable(userId, flag).run();


        log.error("时间差 {}", Duration.between(now, LocalDateTime.now()).toMillis());
    }

    /**
     * 设置角色
     *
     * @param userId 用户id
     */
    private List<UserRoleRelDTO> putRoles(String userId) {
        UserRoleRelQueryDTO query = new UserRoleRelQueryDTO();
        query.setUserId(Long.valueOf(userId));
        query.setIsPagination(false);
        List<UserRoleRelDTO> userRoleRelDTOS = userRoleRelService.selectByQuery(query);


        put(userId, ROLE_KEY, userRoleRelDTOS);
        putRoleIds(userId, getRoleId(userRoleRelDTOS));
        return userRoleRelDTOS;
    }

    /**
     * 设置权限
     *
     * @param userId 用户id
     */
    private void putRoleIds(String userId) {
        putRoleIds(userId, getRoleId(this.get(userId, ROLE_KEY)));
    }

    private List<Long> getRoleId(List<UserRoleRelDTO> userRoleRelDTOS) {
        return getRole(userRoleRelDTOS, UserRoleRelDTO::getRoleId);
    }

    private <T> List<T> getRole(List<UserRoleRelDTO> userRoleRelDTOS, Function<UserRoleRelDTO, T> function) {
        return userRoleRelDTOS.stream().filter(userRoleRelDTO -> userRoleRelDTO.getStatus() == 1).map(function).collect(Collectors.toList());
    }

    protected boolean isAdmin(String userId, List<Long> roleIds) {
        return adminSave.getIds().contains(Long.valueOf(userId)) || roleIds.contains(1L);
    }

    /**
     * 设置权限
     *
     * @param userId  用户id
     * @param roleIds 该用户的角色列表
     */
    private void putRoleIds(String userId, List<Long> roleIds) {
        UserPermissionsRoleRelQueryDTO userPermissionsRoleRelQueryDTO;
        List<UserPermissionsRoleRelDTO> userPermissionsRoleRelDTOS;

        //当为管理员时, 停用的也需要使用
        if (isAdmin(userId, roleIds)) {
            UserPermissionsQueryDTO userPermissionsQueryDTO = new UserPermissionsQueryDTO();
            userPermissionsQueryDTO.setIsPagination(false);
            List<UserPermissionsDTO> userPermissionsDTOS = userPermissionsService.selectByQuery(userPermissionsQueryDTO);

            userPermissionsRoleRelDTOS = userPermissionsDTOS.stream().map(userPermissionsDTO -> {
                UserPermissionsRoleRelDTO userPermissionsRoleRelDTO = userPermissionsRoleRelService.cover(userPermissionsDTO);
                userPermissionsRoleRelDTO.setRoleCode(ADMIN);
                userPermissionsRoleRelDTO.setRoleId(1L);
                userPermissionsRoleRelDTO.setRoleName("超级管理员");
                userPermissionsRoleRelDTO.setRoleType(1);
                userPermissionsRoleRelDTO.setPermissionsId(userPermissionsDTO.getId());
                userPermissionsRoleRelDTO.setPermissionsName(userPermissionsDTO.getName());
                userPermissionsRoleRelDTO.setPermissionsCode(userPermissionsDTO.getCode());

                return userPermissionsRoleRelDTO;
            }).collect(Collectors.toList());
        } else {
            userPermissionsRoleRelQueryDTO = new UserPermissionsRoleRelQueryDTO();
            userPermissionsRoleRelQueryDTO.setRoleIds(roleIds);
            userPermissionsRoleRelQueryDTO.setStatus(1);
            userPermissionsRoleRelQueryDTO.setIsPagination(false);
            userPermissionsRoleRelDTOS = userPermissionsRoleRelService.selectByQuery(userPermissionsRoleRelQueryDTO);

//            UserPermissionsQueryDTO userPermissionsQueryDTO = new UserPermissionsQueryDTO();
//            userPermissionsQueryDTO.setStatus(1);
//            userPermissionsQueryDTO.setIsPagination(false);
//
//            //所有人的权限
//            List<UserPermissionsRoleRelDTO> anyList = userPermissionsService.selectByQuery(userPermissionsQueryDTO).stream().map(userPermissionsDTO -> {
//                UserPermissionsRoleRelDTO UserPermissionsRoleRelDTO = userPermissionsRoleRelService.cover(userPermissionsDTO);
//                UserPermissionsRoleRelDTO.setRoleCode(ANY);
//                UserPermissionsRoleRelDTO.setRoleId(0L);
//                UserPermissionsRoleRelDTO.setRoleName("所有人");
//
//                return UserPermissionsRoleRelDTO;
//            }).collect(Collectors.toList());
//
//            List<Long> collect = userPermissionsRoleRelDTOS.stream().map(UserPermissionsRoleRelDTO::getPermissionsId).collect(Collectors.toList());
//            anyList.forEach(UserPermissionsRoleRelDTO -> {
//                if (!collect.contains(UserPermissionsRoleRelDTO.getId())) {
//                    userPermissionsRoleRelDTOS.add(UserPermissionsRoleRelDTO);
//                }
//            });
        }

        putPermission(userId, userPermissionsRoleRelDTOS);
    }

    private void putPermission(String userId, List<UserPermissionsRoleRelDTO> userPermissionsRoleRelDTOList) {
        //进行分类, 设置menu, 进行排序
        Map<Long, List<UserPermissionsRoleRelDTO>> groupParentId = userPermissionsRoleRelDTOList.stream().collect(Collectors.groupingBy(UserPermissionsRoleRelDTO::getParentId, Collectors.toList()));

        userPermissionsRoleRelService.repairData(groupParentId);

        userPermissionsRoleRelDTOList = new ArrayList<>();

        groupParentId.values().forEach(userPermissionsRoleRelDTOList::addAll);
        userPermissionsRoleRelDTOList = userPermissionsRoleRelDTOList.stream().distinct().collect(Collectors.toList());

        put(userId, PERMISSIONS_KEY, userPermissionsRoleRelDTOList);
        put(userId, PERMISSIONS_LIST_KEY, userPermissionsRoleRelDTOList.stream().map(UserPermissionsRoleRelDTO::getPermissionsCode).collect(Collectors.toList()));
        putMenu(userId, groupParentId);
    }

    private void putMenu(String userId, List<UserPermissionsRoleRelDTO> userPermissionsRoleRelDTOList) {
        //进行分类, 设置menu, 进行排序
        Map<Long, List<UserPermissionsRoleRelDTO>> groupParentId = userPermissionsRoleRelDTOList.stream().collect(Collectors.groupingBy(UserPermissionsRoleRelDTO::getParentId, Collectors.toList()));

        if (MapUtils.isNotEmpty(groupParentId)) {
            userPermissionsRoleRelService.repairData(groupParentId);
        }

        putMenu(userId, groupParentId);
    }

    private void putMenu(String userId, Map<Long, List<UserPermissionsRoleRelDTO>> groupParentId) {
        if (MapUtils.isNotEmpty(groupParentId)) {
            List<UserPermissionsRoleRelDTO> parent = groupParentId.get(0L).stream().sorted(Comparator.comparing(UserPermissionsRoleRelDTO::getSortIndex)).collect(Collectors.toList());
            parent.forEach(UserPermissionsRoleRelDTO -> setChild(UserPermissionsRoleRelDTO, groupParentId));

            put(userId, MENU_KEY, parent);
        } else {
            put(userId, MENU_KEY, Collections.emptyList());
        }
    }

    protected abstract void putOther(String userId);

    private void setChild(UserPermissionsRoleRelDTO parentPerissions, Map<Long, List<UserPermissionsRoleRelDTO>> groupParentId) {
        List<UserPermissionsRoleRelDTO> UserPermissionsRoleRelDTOS = groupParentId.get(parentPerissions.getPermissionsId());

        if (CollectionUtils.isEmpty(UserPermissionsRoleRelDTOS)) {
            parentPerissions.setLeaf(true);
            return;
        }

        List<UserPermissionsRoleRelDTO> perissions = new ArrayList<>();

        //去掉重复的数据
        for(UserPermissionsRoleRelDTO userPermissionsRoleRelDTO:UserPermissionsRoleRelDTOS){
            if(perissions.stream().noneMatch(userPermissionsRoleRelDTO1 -> userPermissionsRoleRelDTO.getPermissionsId().equals(userPermissionsRoleRelDTO1.getPermissionsId()))){
                perissions.add(userPermissionsRoleRelDTO);
            }
        }

        perissions = perissions.stream().sorted(Comparator.comparing(UserPermissionsRoleRelDTO::getSortIndex)).collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(UserPermissionsRoleRelDTOS)) {
            UserPermissionsRoleRelDTOS.forEach(UserPermissionsRoleRelDTO -> setChild(UserPermissionsRoleRelDTO, groupParentId));

            parentPerissions.setLeaf(perissions.stream().noneMatch(UserPermissionsRoleRelDTO -> UserPermissionsRoleRelDTO.getType() == 1));
            parentPerissions.setChild(perissions);
        }
    }

    @Override
    public List<UserPermissionsRoleRelDTO> getMenu(Long userId) {
        return getMenu(userId, 1L);
    }

    @Override
    public List<UserPermissionsRoleRelDTO> getMenu(Long userId, Long parentId) {
        Optional<UserPermissionsRoleRelDTO> userPermissionsRoleRelDTO = this.<UserPermissionsRoleRelDTO>get(userId.toString(), MENU_KEY).stream().filter(temp -> temp.getPermissionsId().equals(parentId)).findFirst();

        if (userPermissionsRoleRelDTO.isPresent()) {
            return userPermissionsRoleRelDTO.get().getChild();
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    public List<UserPermissionsRoleRelDTO> getTop(Long userId) {
        List<UserPermissionsRoleRelDTO> userPermissionsRoleRelDTOList = this.<UserPermissionsRoleRelDTO>get(userId.toString(), MENU_KEY).stream().filter(temp -> temp.getParentId().equals(0L) && temp.getType() != 3).collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(userPermissionsRoleRelDTOList)) {
            userPermissionsRoleRelDTOList.forEach(temp -> {
                if (temp.getChild() != null) {
                    temp.getChild().forEach(t -> {
                        t.setChild(null);
                    });
                }
            });

            return userPermissionsRoleRelDTOList;
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    public List<UserPermissionsRoleRelDTO> getIndex(Long userId) {
        UserPermissionsRoleRelQueryDTO query = new UserPermissionsRoleRelQueryDTO();
        query.setUserId(userId);
        query.setIsPagination(false);
        query.setStatus(1);

        return userPermissionsRoleRelService.selectByQuery(query);
    }

    @Override
    public void onApplicationEvent(AbstractChangeEvent event) {
        if (!StringUtils.equals("true", System.getProperty("menu", "true"))) {
            return;
        }

        synchronized (LOCK) {
            if (event instanceof RoleChangeEvent && event.getType() != BaseService.BASE_OP.INSERT) {
                UserRoleDTO userRoleDTO = null;

                if (event.getType() == BaseService.BASE_OP.UPDATE) {
                    userRoleDTO = OptionalUtils.get(userRoleService.selectById(event.getId()));
                }

                UserRoleDTO finalUserRoleDTO = userRoleDTO;

                keys().forEach(key -> {
                    String userId = key.replace(ROLE_SAVE_KEY, "");
                    List<UserRoleRelDTO> userRoleRelDTOS = get(userId, ROLE_KEY);

                    if (CollectionUtils.isNotEmpty(userRoleRelDTOS)) {
                        Optional<UserRoleRelDTO> first = userRoleRelDTOS.stream().filter(role -> role.getRoleId().equals(event.getId())).findFirst();

                        if (first.isPresent()) {
                            UserRoleRelDTO role = first.get();
                            boolean updatePermissions = false;
                            if (event.getType() == BaseService.BASE_OP.UPDATE && finalUserRoleDTO != null) {
                                updatePermissions = !role.getStatus().equals(finalUserRoleDTO.getStatus());
                                role.setRoleName(finalUserRoleDTO.getName());
                                role.setStatus(finalUserRoleDTO.getStatus());
                            } else if (event.getType() == BaseService.BASE_OP.DELETE) {
                                userRoleRelDTOS.remove(role);
                                updatePermissions = true;
                            }

                            put(userId, ROLE_KEY, userRoleRelDTOS);

                            if (updatePermissions) {
                                putRoleIds(userId, getRoleId(userRoleRelDTOS));
                            }

                            publishEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.ROLE);
                        }
                    }
                });
            } else if (event instanceof PermissionsChangeEvent) {
                UserPermissionsDTO userPermissionsDTO = null;

                if (event.getType() != BaseService.BASE_OP.DELETE) {
                    userPermissionsDTO = OptionalUtils.get(userPermissionsService.selectById(event.getId()));
                }

                UserPermissionsDTO finalUserPermissionsDTO = userPermissionsDTO;
                keys().forEach(key -> {
                    String userId = key.replace(ROLE_SAVE_KEY, "");
                    List<UserPermissionsRoleRelDTO> userPermissionsRoleRelDTOS = get(userId, PERMISSIONS_KEY);

                    //停用或者启用时重新处理菜单
                    if (event.getType() == BaseService.BASE_OP.UPDATE) {
                        Optional<UserPermissionsRoleRelDTO> first = userPermissionsRoleRelDTOS.stream().filter(UserPermissionsRoleRelDTO -> UserPermissionsRoleRelDTO.getPermissionsId().equals(event.getId())).findFirst();

                        if (finalUserPermissionsDTO != null && (!first.isPresent() || finalUserPermissionsDTO.getStatus() == 0 || !first.get().getParentId().equals(finalUserPermissionsDTO.getParentId()) || !StringUtils.equals(finalUserPermissionsDTO.getName(), first.get().getPermissionsCode()))) {
                            putRoleIds(userId);
                            publishEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.PERMISSIONS);
                            return;
                        }
                    } else if (event.getType() == BaseService.BASE_OP.INSERT && finalUserPermissionsDTO != null) {
                        List<UserRoleRelDTO> userRoleRelDTOS = get(userId, ROLE_KEY);

                        if (isAdmin(userId, userRoleRelDTOS.stream().map(UserRoleRelDTO::getRoleId).collect(Collectors.toList())) || userPermissionsRoleRelDTOS.stream().anyMatch(UserPermissionsRoleRelDTO -> UserPermissionsRoleRelDTO.getPermissionsId().equals(finalUserPermissionsDTO.getParentId()))) {
                            putRoleIds(userId);
                            publishEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.PERMISSIONS);
                            return;
                        }
                    }

                    if (CollectionUtils.isNotEmpty(userPermissionsRoleRelDTOS)) {
                        List<UserPermissionsRoleRelDTO> list = userPermissionsRoleRelDTOS.stream().filter(role -> role.getPermissionsId().equals(event.getId())).collect(Collectors.toList());

                        if (list.size() > 0) {
                            list.forEach(permissions -> {
                                if (event.getType() == BaseService.BASE_OP.UPDATE && finalUserPermissionsDTO != null) {
                                    userPermissionsRoleRelService.cover(finalUserPermissionsDTO, permissions);
                                } else if (event.getType() == BaseService.BASE_OP.DELETE) {
                                    userPermissionsRoleRelDTOS.remove(permissions);
                                }
                            });

                            putPermission(userId, userPermissionsRoleRelDTOS);
                            publishEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.PERMISSIONS);
                        }
                    }
                });
            } else if (event instanceof PermissionsRelChangeEvent) {
                PermissionsRelChangeEvent permissionsRelChangeEvent = (PermissionsRelChangeEvent) event;

                keys().forEach(key -> {
                    String userId = key.replace(ROLE_SAVE_KEY, "");
                    List<UserRoleRelDTO> userRoleRelDTOS = get(userId, ROLE_KEY);

                    if (CollectionUtils.isNotEmpty(userRoleRelDTOS)) {
                        if (userRoleRelDTOS.stream().anyMatch(role -> permissionsRelChangeEvent.getRoleId().equals(role.getRoleId()))) {
                            putRoleIds(userId, getRoleId(userRoleRelDTOS));
                            publishEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.PERMISSIONS);
                        }
                    }
                });
            } else if (event instanceof RoleRelChangeEvent) {
                String userId = ((RoleRelChangeEvent) event).getUserId().toString();
                putRoles(userId);
                publishEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.ROLE);
            } else if (event instanceof UserStatusChangeEvent) {
                UserStatusChangeEvent userStatusChangeEvent = (UserStatusChangeEvent) event;
                remove(event.getId().toString());
                Map<String, Session> map = sessionRepository.findByPrincipalName(userStatusChangeEvent.getId().toString());

                map.forEach((sessionId, session) -> {
                    LoginData loginData = session.getAttribute(Constants.SESSION_LOGIN_DATE);
                    loginData.setStatus(userStatusChangeEvent.getStatus());
                });
            } else if (event instanceof OrganRelChangeEvent && event.getType() != BaseService.BASE_OP.INSERT) {
                keys().forEach(key -> {
                    String userId = key.replace(ROLE_SAVE_KEY, "");
                    publishEvent(userId, PermissionsUpdateEvent.PermissionsTypeEnum.ORGAN);
                });
            }
        }
    }

    private void publishEvent(String userId, PermissionsUpdateEvent.PermissionsTypeEnum type) {
        if (THREAD_LOCAL.get()) {
            SpringContextUtil.publishEvent(new PermissionsUpdateEvent(userId, type));
        }
    }

    public class ParseRunnable implements Runnable {
        private String userId;
        private boolean flag;

        ParseRunnable(String userId, boolean flag) {
            this.userId = userId;
            this.flag = flag;
        }

        @Override
        public void run() {
            //缓存处理当前用户的权限
            if (!hasKey(userId) || flag) {
                try {
                    putRoles(this.userId);
                    putOther(this.userId);
                } catch (Exception e) {
                    log.error("parse role exception", e);
                    remove(userId);
                }
            }
        }
    }
}
